package disk

import (
	"bufio"
	"bytes"
	"encoding/json"
	"github.com/sirupsen/logrus"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"
)

func PartitionExtend(path string) error {
	cmd := exec.Command("resize2fs", path)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	logrus.Debugf("%+v", cmd.Args)
	err := cmd.Run()
	logrus.Debugf("result %v, error %v", stdout.String(), stderr.String())
	return err
}

type PartitionMount struct {
	Source   string
	Target   string
	Size     int64
	UsedSize int64
	FreeSize int64
}

var (
	_dfMu    sync.Mutex
	_dfCache []*PartitionMount
	_dfAt    time.Time
)

func PartitionMountList() []*PartitionMount {
	_dfMu.Lock()
	defer _dfMu.Unlock()
	if _dfCache != nil && _dfAt.Add(time.Second*10).After(time.Now()) {
		return _dfCache
	}
	cmd := exec.Command("df", "-k")
	logrus.Debugf("%+v", cmd.Args)
	out, err := cmd.Output()
	if err != nil {
		return nil
	}
	_dfCache = processDF(out)
	_dfAt = time.Now()
	return _dfCache
}

//Filesystem     1K-blocks      Used Available Use% Mounted on
//dev             16313684         0  16313684   0% /dev
//run             16323420      1964  16321456   1% /run
///dev/nvme1n1p3 173322332 139221236  25227112  85% /
//tmpfs           16323420    190532  16132888   2% /dev/shm
//tmpfs           16323424    116040  16207384   1% /tmp
///dev/nvme0n1p2 239301252 177266812  49808956  79% /home/yplam/work
///dev/nvme1n1p4    408784       316    408468   1% /boot/efi
///dev/sda5      250932080 164801612  73314084  70% /home/yplam/backup
//tmpfs            3264684       128   3264556   1% /run/user/1000
///dev/yctc/lva      93518        14     86336   1% /mnt/zt-smartassistant/volume/yctc/lva

func processDF(result []byte) []*PartitionMount {
	scanner := bufio.NewScanner(bytes.NewReader(result))
	skipdHead := false
	pms := make([]*PartitionMount, 0)
	for scanner.Scan() {
		if !skipdHead {
			skipdHead = true
			continue
		}
		sps := strings.Fields(strings.TrimSpace(scanner.Text()))
		logrus.Debugf("%+v", sps)
		if len(sps) < 6 {
			continue
		}
		source := sps[0]
		pm := PartitionMount{
			Source: source,
			Target: sps[5],
			// size_unit: B
			Size:     parseKBSize(sps[1]),
			UsedSize: parseKBSize(sps[2]),
			FreeSize: parseKBSize(sps[3]),
		}
		pms = append(pms, &pm)
	}
	return pms
}

func parseKBSize(str string) int64 {
	s, err := strconv.Atoi(str)
	if err != nil {
		return 0
	}
	return int64(s) * 1024
}

// IsPartitionMountOn 检查 source 对应的分区是否挂载在 target 下
func IsPartitionMountOn(source string, target string) (isMount bool, isTarget bool) {
	pms := PartitionMountList()
	for _, v := range pms {
		if v.Source == source {
			isMount = true
			if v.Target == target {
				isTarget = true
			}
		}
	}
	return
}

// BlockDevice
//{
//   "blockdevices": [
//      {
//         "name": "sda",
//         "path": "/dev/sda",
//         "fssize": null,
//         "fstype": null,
//         "fsused": null,
//         "mountpoint": null,
//         "size": 500107862016,
//         "ro": false,
//         "rm": false,
//         "type": "disk",
//         "children": [
//            {
//               "name": "sda1",
//               "path": "/dev/sda1",
//               "fssize": null,
//               "fstype": "vfat",
//               "fsused": null,
//               "mountpoint": null,
//               "size": 272629760,
//               "ro": false,
//               "rm": false,
//               "type": "part"
//            },{
//               "name": "sda2",
//               "path": "/dev/sda2",
//               "fssize": null,
//               "fstype": null,
//               "fsused": null,
//               "mountpoint": null,
//               "size": 16777216,
//               "ro": false,
//               "rm": false,
//               "type": "part"
//            },{
//               "name": "sda3",
//               "path": "/dev/sda3",
//               "fssize": null,
//               "fstype": "ntfs",
//               "fsused": null,
//               "mountpoint": null,
//               "size": 236624805888,
//               "ro": false,
//               "rm": false,
//               "type": "part"
//            },{
//               "name": "sda4",
//               "path": "/dev/sda4",
//               "fssize": null,
//               "fstype": "ntfs",
//               "fsused": null,
//               "mountpoint": null,
//               "size": 1048576000,
//               "ro": false,
//               "rm": false,
//               "type": "part"
//            },{
//               "name": "sda5",
//               "path": "/dev/sda5",
//               "fssize": "256954449920",
//               "fstype": "ext4",
//               "fsused": "168756850688",
//               "mountpoint": "/home/yplam/backup",
//               "size": 262144000000,
//               "ro": false,
//               "rm": false,
//               "type": "part"
//            }
//         ]
//      }
//   ]
//}
// lsblk -J -b -o NAME,PATH,FSSIZE,FSTYPE,FSUSED,MOUNTPOINT,SIZE,RO,RM,TYPE
type BlockDevice struct {
	Name       string         `json:"name"`
	Path       string         `json:"path"`
	Size       int64          `json:"size"`
	FSSize     int64          `json:"fssize,string"`
	FSType     string         `json:"fstype"`
	FSUsed     int64          `json:"fsused,string"`
	MountPoint string         `json:"mountpoint"`
	RO         bool           `json:"ro"`
	RM         bool           `json:"rm"`
	Type       string         `json:"type"`
	Children   []*BlockDevice `json:"children"`
}

var (
	_bdMu    sync.Mutex
	_bdCache []*BlockDevice
	_bdAt    time.Time
)

func BlockDeviceList() []*BlockDevice {
	_bdMu.Lock()
	defer _bdMu.Unlock()
	if _bdCache != nil && _bdAt.Add(time.Second*10).After(time.Now()) {
		return _bdCache
	}
	cmd := exec.Command("lsblk", "-J", "-b", "-o",
		"NAME,PATH,FSSIZE,FSTYPE,FSUSED,MOUNTPOINT,SIZE,RO,RM,TYPE")
	logrus.Debugf("%+v", cmd.Args)
	out, err := cmd.Output()
	if err != nil {
		return nil
	}
	_bdCache = processLSBLK(out)
	_bdAt = time.Now()
	return _bdCache
}

func processLSBLK(data []byte) []*BlockDevice {
	bds := struct {
		Blockdevices []*BlockDevice `json:"blockdevices"`
	}{
		Blockdevices: make([]*BlockDevice, 0),
	}
	err := json.Unmarshal(data, &bds)
	if err != nil {
		logrus.Warnf("can not decode json %v", err)
		return nil
	}
	return bds.Blockdevices
}

func GetPartition(path string) *BlockDevice {
	logrus.Infof("get partition %s", path)
	bds := BlockDeviceList()
	for _, v := range bds {
		if v.Path == path {
			return v
		}
		if len(v.Children) > 0 {
			for _, vv := range v.Children {
				if vv.Path == path {
					return v
				}
			}
		}
	}
	return nil
}

func _firstNoneZero(vals ...int64) int64 {
	for _, v := range vals {
		if v > 0 {
			return v
		}
	}
	return 0
}

func GetPartitionSizes(path string) (pSize int64, fsSize int64, fsFree int64) {
	if bd := GetPartition(path); bd != nil {
		pSize = bd.Size
		fsSize = bd.FSSize
		fsFree = bd.FSSize - bd.FSUsed
	}
	// 再尝试用 df 命令获取
	if pSize == 0 || fsSize == 0 || fsFree == 0 {
		pms := PartitionMountList()
		for _, v := range pms {
			if v.Source == path {
				pSize = _firstNoneZero(pSize, v.Size)
				fsSize = _firstNoneZero(fsSize, v.Size)
				fsFree = _firstNoneZero(fsFree, v.FreeSize)
			}
		}
	}
	return
}

func TryUnmountPartition(path string) {
	bd := GetPartition(path)
	if bd == nil {
		return
	}
	if len(bd.MountPoint) == 0 {
		return
	}
	err := syscall.Unmount(bd.MountPoint, 0)
	if err != nil {
		logrus.Warnf("can not umount %v", bd.MountPoint)
	}
}
